package com.aforce3.trailertracer.scenes;

import javax.media.j3d.Transform3D;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix3d;
import javax.vecmath.Point3d;
import javax.vecmath.SingularMatrixException;
import javax.vecmath.Vector3d;

import com.aforce3.trailertracer.Frame;
import com.aforce3.trailertracer.Ray;
import com.aforce3.trailertracer.optimizations.BoundingBox;
import com.aforce3.trailertracer.optimizations.SceneBoundingBox;
import com.aforce3.trailertracer.utils.Color;
import com.aforce3.trailertracer.utils.ColorModifier;

public class Triangle extends Primitive
{
	Point3d a;
	Point3d b;
	Point3d c;
	
	Point3d unTransformeda;
	Point3d unTransformedb;
	Point3d unTransformedc;
	
	BoundingBox boundingBox;

	public Triangle(Point3d a, Point3d b, Point3d c)
	{
		this.a = a;
		this.unTransformeda = a;
		this.b = b;
		this.unTransformedb = b;
		this.c = c;
		this.unTransformedc = c;
	}

	/**
	 * Gets the color of the intersection of the ray and the scene. Return null
	 * if there is no intersection
	 **/
	public Color3f getColor(Ray ray, Scene child)
	{
		Vector3d result = calculateRay(ray);
		Point3d intersect = new Point3d();
		Vector3d temp = new Vector3d(ray.getDirection());
		temp.scale(result.x);
		temp.add(ray.getSource());
		intersect.x = temp.x;
		intersect.y = temp.y;
		intersect.z = temp.z;

		// If we actually hit the triangle
		if((result.y <= 1 && result.y >= 0) && (result.z <= 1 && result.z >= 0) && ((result.y + result.z) <= 1))
		{
			Color3f reflectedColor = null;
			Ray newRay = null;
			// point is in triangle, return color
			if(ray.depth < Frame.depth && material.getReflectivity() > 0)
			{
				// Finds the ray reflecting back off the triangle
				newRay = calculateReflection(ray, intersect);

				// System.out.println(newRay.direction);
				// System.out.println("Intersection: " + newRay.source);

				reflectedColor = getRayColor(newRay);
			}

			if(reflectedColor == null)
			{
				return ColorModifier.calculateLight(getRoot(), this, newRay, intersect);
			}
			else
			{
				//System.out.println("reflecting");
				// dot product gives shade
				Color3f ambient = ColorModifier.calculateLight(getRoot(), this, ray, intersect);
				//Color inverseColor = new Color(1 - material.getAmbientColor().x, 1 - material.getAmbientColor().y, 1 - material.getAmbientColor().z);
				Color returnColor = new Color(reflectedColor);
				//returnColor.sub(inverseColor);
				returnColor.scale(material.getReflectivity());
				returnColor.add(ambient);
				returnColor.clamp(0, 1);
				return returnColor;
				//return ambient;
			}
		}

		return null;
	}

	public float getDistance(Ray ray)
	{
		Vector3d result = calculateRay(ray);

		if((result.y <= 1 && result.y >= 0) && (result.z <= 1 && result.z >= 0) && ((result.y + result.z) <= 1))
		{
			// point is in triangle, return color
			return (float) result.x;
		}
		return -1;
	}

	/**
	 * Does some magic to give you the distance away the source of the array
	 */
	public Vector3d calculateRay(Ray ray)
	{
		Matrix3d matrix = new Matrix3d();
		Vector3d BminusA = new Vector3d();
		BminusA.add(b);
		BminusA.sub(a);
		Vector3d CminusA = new Vector3d();
		CminusA.add(c);
		CminusA.sub(a);
		Vector3d PminusA = new Vector3d();
		PminusA.add(ray.getSource());
		PminusA.sub(a);

		Vector3d SminusD = new Vector3d();
		// SminusD.add(ray.getSource());
		SminusD.sub(ray.getDirection());

		matrix.setColumn(0, SminusD);
		matrix.setColumn(1, BminusA);
		matrix.setColumn(2, CminusA);

		try
		{
			matrix.invert();
		}
		catch(SingularMatrixException e)
		{
			// not invertible (ray parallel)
			return new Vector3d(-1, -1, -1);
		}

		Vector3d result = new Vector3d();
		matrix.transform(PminusA, result);

		return result;
	}

	public Vector3d getNormal(Ray ray)
	{
		Vector3d BminusA = new Vector3d();
		BminusA.add(b);
		BminusA.sub(a);
		Vector3d CminusA = new Vector3d();
		CminusA.add(c);
		CminusA.sub(a);
		Vector3d normal = new Vector3d();

		normal.cross(BminusA, CminusA);
		normal.normalize();

		// System.out.println(normal);

		return normal;
	}
	
	public String toString()
	{
		return "triangle("+a+","+b+","+c+")";
	}

	@Override
	protected void applyTransform(Transform3D t)
	{
		t.transform(a);
		t.transform(b);
		t.transform(c);
		//boundingBox.applyTransform(t);
	}

	@Override
	public void applyTransform()
	{
		a = new Point3d(unTransformeda);
		transformation.transform(a);
		b = new Point3d(unTransformedb);
		transformation.transform(b);
		c = new Point3d(unTransformedc);
		transformation.transform(c);
		
		//boundingBox.applyTransform(transformation);
	}
	
	public BoundingBox getBoundingBox()
	{
		double minx = Math.min(a.x, b.x);
		minx = Math.min(minx, c.x);
		double maxx = Math.max(a.x, b.x);
		maxx = Math.max(maxx, c.x);
		
		double miny = Math.min(a.y, b.y);
		miny = Math.min(miny, c.y);
		double maxy = Math.max(a.y, b.y);
		maxy = Math.max(maxy, c.y);
		
		double minz = Math.min(a.z, b.z);
		minz = Math.min(minz, c.z);
		double maxz = Math.max(a.z, b.z);
		maxz = Math.max(maxz, c.z);
		
		
		return new SceneBoundingBox(new Point3d(minx, maxy, minz), new Point3d(maxx, miny, maxz), this);
	}
}
